
package cn.edu.dutir.utility;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;


/**
 * A <code>MapSymbolTable</code > is a dynamic symbol table based on a
 * pair of underlying maps.  After creating a map symbol table, new
 * symbols may be added using {@link #getOrAddSymbol(String)}.
 *
 * <p>Map symbol tables are serializable.  The result of writing
 * to an object output stream and reading back in through an
 * object input stream produces an instance of this same class,
 * <code>MapSymbolTable</code>, with the same behavior as the
 * instance serialized.
 *
 * <P><i>Implementation Note:</i> This table uses a pair of
 * maps, one in each direction between symbols represented
 * by instances of <code>String</code> and identifiers
 * represented by instance of <code>Integer</code>.
 *
 */
public class SymbolTable implements Serializable {

    /**
	 * 
	 */
	private static final long serialVersionUID = 4472485002859541690L;
	final HashMap<String,Integer> mSymbolToId = new HashMap<String,Integer>();
    final HashMap<Integer,String> mIdToSymbol = new HashMap<Integer,String>();
    private int mNextSymbol = 0;

    /**
     * Construct an empty map symbol table.  The default first
     * symbol identifier is zero (<code>0</code>) and subsequent
     * symbols are assigned successive integer identifiers.
     */
    public SymbolTable() {
        this(0);
    }

    /**
     * Construct an empty map symbol table that begins allocating
     * identifiers at the specified value.  Subsequent symbols
     * will be assigned identifiers to successive identifiers.
     *
     * @param firstId Identifier of first symbol.
     */
    public SymbolTable(int firstId) {
        mNextSymbol = firstId;
    }

    private SymbolTable(ObjectInput objIn) throws IOException {
		int numEntries = objIn.readInt();
		int max = 0;
		for (int i = 0; i < numEntries; ++i) {
			String symbol = objIn.readUTF();
			Integer id = new Integer(objIn.readInt());
			max = Math.max(max, id.intValue());
			mSymbolToId.put(symbol, id);
			mIdToSymbol.put(id, symbol);
		}
		mNextSymbol = max + 1;
	}
    
    /**
     * Returns the complete set of symbols in this symbol table.
     *
     * @return The set of symbols for this symbol table.
     */
    public Set<String> symbolSet() {
        return Collections.<String>unmodifiableSet(mSymbolToId.keySet());
    }
    
    /**
     * Returns the complete set of ids in this id table.
     * 
     * @return The set of ids for this ids table
     */
    public Set<Integer> idSet() {
		return Collections.<Integer> unmodifiableSet(mIdToSymbol.keySet());
	}
    
    public int numSymbols() {
		return mSymbolToId.size();
	}

    public int symbolToID(String symbol) {
		Integer result = symbolToIDInteger(symbol);
		return result == null ? -1 : result.intValue();
	}

    /**
	 * Returns an Integer representation of the symbol if it exists in the table
	 * or null if it does not.
	 * 
	 * @param symbol
	 *            Symbol whose identifer is returned.
	 * @return Integer identifier for symbol, or null if it does not exist.
	 */
    public Integer symbolToIDInteger(String symbol) {
		return mSymbolToId.get(symbol);
	}

    /**
	 * Returns the symbol for the specified identifier. If the identifier has no
	 * defined symbol, an exception is thrown.
	 * 
	 * @param id
	 *            Integer identifier.
	 * @return The symbol for the identifier.
	 * @throws IndexOutOfBoundsException
	 *             If the symbol could not be found in the symbol table.
	 */
    public String idToSymbol(Integer id) {
        String symbol = mIdToSymbol.get(id);
        if (symbol == null) {
            String msg="Could not find id=" + id;
            throw new IndexOutOfBoundsException(msg);
        }
        return symbol;
    }

    public String idToSymbol(int id) {
        return idToSymbol(new Integer(id));
    }

    /**
     * Removes the specified symbol from the symbol table.  After the
     * symbol is removed, its identifier will not be assigned to
     * another symbol.
     *
     * @param symbol Symbol to remove.
     * @return The previous id of the symbol if it was in the table,
     * or -1 if it was not.
     */
    public int removeSymbol(String symbol) {
        int id = symbolToID(symbol);
        if (id >= 0) {
            mSymbolToId.remove(symbol);
            mIdToSymbol.remove(new Integer(id));
        }
        return id;
    }

    /**
     * Clears all of the symbols from the symbol table.  It does
     * not reset the symbol counter, so the removed identifiers
     * will not be reused.
     */
    public void clear() {
        mSymbolToId.clear();
        mIdToSymbol.clear();
    }

    /**
     * Returns the identifier for the specified symbol, adding
     * it to the symbol table if necessary.
     *
     * @param symbol Symbol to get or add to the table.
     * @return Identifier for specified symbol.
     */
    public int getOrAddSymbol(String symbol) {
        return getOrAddSymbolInteger(symbol).intValue();
    }

    /**
     * Returns the integer identifier for the specified symbol,
     * adding it to the symbol table if necessary.
     *
     * @param symbol Symbol to get or add to the table.
     * @return Identifier for specified symbol.
     */
    public Integer getOrAddSymbolInteger(String symbol) {
        Integer id = mSymbolToId.get(symbol);
        if (id != null) return id;
        Integer freshId = new Integer(mNextSymbol++);
        mSymbolToId.put(symbol,freshId);
        mIdToSymbol.put(freshId,symbol);
        return freshId;
    }

    /**
     * Returns a string-based representation of this symbol table
     * by printing the underlying identifier to symbol mapping.
     *
     * @return A string-based representation of this symbol table.
     */
    public String toString() {
        return mIdToSymbol.toString();
    }
    
    /**
     * Formatted the string-based representation of this SymbolTalb object.
     */
    public void symbolToIdStringBuffer(StringBuffer sb) {
		sb.setLength(0);
		sb.append(mIdToSymbol.size() + "\r\n");
		Set<String> symbolSet = mSymbolToId.keySet();
		Iterator<String> iter = symbolSet.iterator();
		while (iter.hasNext()) {
			String symbol = iter.next();
			sb.append(symbol + " <--> " + symbolToID(symbol) + "\r\n");
		}
	}

    /**
     * Formatted the string-based representation of this SymbolTalb object.
     */
	public void idToSymbolStringBuffer(StringBuffer sb) {
		sb.setLength(0);
		sb.append(mIdToSymbol.size() + "\r\n");
		Set<Integer> idSet = mIdToSymbol.keySet();
		Iterator<Integer> iter = idSet.iterator();
		while (iter.hasNext()) {
			Integer id = iter.next();
			sb.append(id + " <--> " + idToSymbol(id) + "\r\n");
		}
	}
    
	/**
	 * 
	 * Serialize current SymbolTable object into the specified object output
	 * stream.
	 */
    public void writeReplace(ObjectOutput objOut) throws IOException{
    	new Externalizer(this).writeExternal(objOut);
    }
    
    /**
     * Read in a SymbolTable object from the specified object input stream.
     */
    public static SymbolTable readObject(ObjectInput in) throws IOException {
		return new SymbolTable(in);
	}
    
	/**
	 * A Externalizer is a serialization class used for outputting or read in a
	 * instance of the SymbolTable object.
	 * 
	 */
    private static class Externalizer {
		private static final long serialVersionUID = -6040616216389802649L;
		final SymbolTable mSymbolTable;

		public Externalizer() {
			mSymbolTable = null;
		}

		public Externalizer(SymbolTable symbolTable) {
			mSymbolTable = symbolTable;
		}

		/**
		 * Read in a SymbolTable object from the specified object input stream.
		 * 
		 * @param in The specified object input stream.
		 * @return A instance of SymbolTable object.
		 */
		public Object read(ObjectInput in) throws IOException {
			return new SymbolTable(in);
		}

		/**
		 * Serialize current SymbolTable object into the specified object
		 * output stream.
		 * 
		 * @param objOut The specified object output stream.
		 * @throws IOException
		 */
		public void writeExternal(ObjectOutput objOut) throws IOException {
			objOut.writeInt(mSymbolTable.mSymbolToId.size());
			Iterator it = mSymbolTable.mSymbolToId.entrySet().iterator();
			while (it.hasNext()) {
				Map.Entry entry = (Map.Entry) it.next();
				objOut.writeUTF(entry.getKey().toString());
				objOut.writeInt(((Integer) entry.getValue()).intValue());
			}
		}
	}
}